#include "qrender/line_renderer.h"

#include <GL/gl.h>

#include <QOpenGLContext>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>

#include <qrender/camera.h>

LineRenderer::LineRenderer() : Renderer()
{
}

LineRenderer::~LineRenderer()
{
    LineRenderer::release();
}

void LineRenderer::setPosition(const QVector3D &pos)
{
    if (pos_ == pos) {
        return;
    }

    pos_ = pos;

    data_changed_ = true;
}

void LineRenderer::setShape(const QVector3D &start, const QVector3D &end,
                            const QVector4D &color)
{
    if (start_ == start && end_ == end && color_ == color) {
        return;
    }

    start_ = start;
    end_ = end;
    color_ = color;

    type_ = LineType_Start2End;

    data_changed_ = true;
}

void LineRenderer::setShape(const QVector<QVector3D> &points,
                            const QVector4D &color)
{
    if (vertices_ == points && color_ == color) {
        return;
    }

    vertices_ = points;
    color_ = color;

    type_ = LineType_Points;

    data_changed_ = true;
}

void LineRenderer::setColor(const QVector4D &color)
{
    if (color_ == color) {
        return;
    }

    color_ = color;
}

void LineRenderer::create()
{
    // 顶点着色器
    const char *vsrc = R"(
#version 330

in vec3 v_pos;

uniform mat4 model_matrix;
uniform mat4 view_matrix;
uniform mat4 projection_matrix;

void main()
{
    gl_Position = projection_matrix * view_matrix * model_matrix * vec4(v_pos, 1.0);
}
)";
    // 片段着色器
    const char *fsrc = R"(
#version 330 core

uniform vec4 f_color;

out vec4 frag_color;

void main()
{
    frag_color = f_color;
}
)";

    // 创建着色器程序
    program_.reset(new QOpenGLShaderProgram());
    program_->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,
                                               vsrc); // 顶点着色器
    program_->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,
                                               fsrc); // 片段着色器
    program_->link();
    program_->bind();

    program_->setUniformValue("view_matrix", QMatrix4x4());
    program_->setUniformValue("projection_matrix", QMatrix4x4());
    program_->setUniformValue("f_color", color_);

    vao_.reset(new QOpenGLVertexArrayObject());
    vao_->create();

    // 生成顶点数据
    vbo_.reset(new QOpenGLBuffer());
    vbo_->create();
    vbo_->bind();

    {
        QOpenGLVertexArrayObject::Binder vao_binder(vao_.get());

        // 告知 OpenGL 如何解析数据, 数据解析格式
        GLuint v_pos = program_->attributeLocation("v_pos");
        program_->setAttributeBuffer(v_pos, GL_FLOAT, 0, 3,
                                     3 * sizeof(GLfloat));
        program_->enableAttributeArray(v_pos);
    }

    vbo_->release();
    program_->release();
}

void LineRenderer::render(const CameraInfo &camera)
{
    // 透明不渲染
    // TODO: 有透明度在后面渲染
    if (color_[3] == 0) {
        return;
    }

    // 更新 OpenGL 配置
    auto f = QOpenGLContext::currentContext()->functions();
    f->glEnable(GL_DEPTH_TEST);
    f->glEnable(GL_LINE_SMOOTH);               // 设置反走样
    f->glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); // 设置反走样
    f->glDisable(GL_CULL_FACE);                // 禁用背面剔除

    // 绑定着色器和缓冲区到当前上下文
    program_->bind();
    vbo_->bind();

    // 相机参数
    if (camera.valid) {
        program_->setUniformValue("view_matrix", camera.view_matrix);
        program_->setUniformValue("projection_matrix",
                                  camera.projection_matrix);
    }

    // 模型数据
    QMatrix4x4 model_matrix;
    model_matrix.translate(pos_);
    program_->setUniformValue("model_matrix", model_matrix);
    program_->setUniformValue("f_color", color_);

    if (data_changed_) {
        switch (type_) {
        case LineType_Start2End:
            vertices_.clear();
            vertices_ << start_ << end_;
            break;
        case LineType_Points:
            // 直接将 points 设置到顶点数组了, 无需再更新
            break;
        default:
            break;
        }

        data_changed_ = false;
    }

    {
        vbo_->allocate(vertices_.constData(),
                       vertices_.size() * sizeof(QVector3D));
        QOpenGLVertexArrayObject::Binder vao_binder(vao_.get());
        f->glLineWidth(2.0);
        switch (type_) {
        case LineType_Start2End:
            f->glDrawArrays(GL_LINES, 0, vertices_.size());
            break;
        case LineType_Points:
            f->glDrawArrays(GL_LINE_STRIP, 0, vertices_.size());
            break;
        default:
            break;
        }
    }

    vbo_->release();
    program_->release();
}

void LineRenderer::release()
{
    vao_.reset();
    vbo_.reset();
    program_.reset();
}
